/* 
 * USB CID Driver - 1.0
 *
 * Copyright (C) 2006 Hansung Chun (adol17@dreamwiz.com)
 *
 *     This file is part of USB CID Driver.
 * 
 *     USB CID Driver is free software; you can redistribute it and/or
 *     modify it under the terms of the GNU General Public License as
 *     published by the Free Software Foundation, version 2.
 *
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <linux/fs.h>		//fops
#include <linux/slab.h>		//kmalloc
#include <asm/uaccess.h>	//copy_from_user
#include <linux/usb.h>

#include "usbcid.h"

#define DRIVER_AUTHOR "Hansung Chun, adol17@dreamwiz.com"
#define DRIVER_DESC "USB CID Function Driver"

#define USBCID_MINOR 160

#define VENDOR_ID 0x0547
#define PRODUCT_ID 0x1005

static struct usb_device_id id_table [] = {
	{ USB_DEVICE(VENDOR_ID, PRODUCT_ID) },
	{},
};
MODULE_DEVICE_TABLE (usb, id_table);

struct usb_cid {
	struct usb_device *udev;
	struct usb_interface *interface;
	__u8 bulk_in_endpointAddr[4];
	__u8 bulk_out_endpointAddr[4];
};

enum { EP1=1, EP2=2, EP3=3, EP4=4 };	//bulk endpoint
static struct usb_driver cid_driver;

static int check_bulk_ep(unsigned int pipe) 
{
	int retval = 0;
	if(pipe == EP1) return 1;
	if(pipe == EP2) return 1;
	if(pipe == EP3) return 1;
	if(pipe == EP4) return 1;
	return retval;
}

static int cid_open(struct inode* inode, struct file* file)
{
	int retval=0;

	struct usb_cid *dev;
	struct usb_interface *interface;
	int subminor;

	subminor = iminor(inode);

	interface = usb_find_interface( &cid_driver, subminor );
	if(!interface) {
		err("USBCID: %s - error, can't find device for minor %d", __FUNCTION__, subminor);
		retval = -ENODEV;
		goto exit;
	}

	dev = usb_get_intfdata(interface);
	if(!dev) {
		err("USBCID: %s - error, can't find dev data for interface", __FUNCTION__);
		retval = -ENODEV;
		goto exit;
	}
	printk("cid_open() - subminor: %d\n", subminor);

	file->private_data = dev;
exit:
	return retval;
}

static int cid_release(struct inode* inode, struct file* file)
{
	printk("cid_release()\n");
	return 0;
}

static int bulk_read(struct usb_cid *dev, unsigned int pipe, char *data_buffer, unsigned int length)
{
	int retval = 0;
	int count = 0;
	char *buf = NULL;
	
	//alloc buf
	buf = kmalloc(length, GFP_KERNEL);
	if(!buf) {
		retval = -ENOMEM;
		goto error;
	}
	memset(buf, 0, length);

	retval = usb_bulk_msg( dev->udev, usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr[pipe]),
				buf, length, &count, HZ*10);
	if(retval) {
		err("USBCID: %s - failed, error %d", __FUNCTION__, retval);
		goto error;
	}

	memcpy( data_buffer, buf, length );
	kfree(buf);
	return 0;
error:
	kfree(buf);
	return retval;
}

static int bulk_write(struct usb_cid *dev, unsigned int pipe, char *data_buffer, unsigned int length)
{
	int retval = 0;
	int count = 0;
	char *buf = NULL;

	//alloc buffer
	buf = kmalloc(length, GFP_KERNEL);
	if(!buf) {
		retval = -ENOMEM;
		goto error;
	}
	
	memcpy(buf, data_buffer, length );	//copy data to buf

	retval = usb_bulk_msg( dev->udev, usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr[pipe]),
				buf, length, &count, HZ*10);
	if(retval) {
		err("USBCID: %s - failed, error %d", __FUNCTION__, retval);
		goto error;
	}

	kfree(buf);
	return 0;
error:
	kfree(buf);
	return retval;
}

static int read_cid(struct usb_cid *dev, unsigned int pipe, char *data_buffer)
{
	int retval = 0;
	char buf[32];

	__u8 data = 0x11;
	unsigned int cid_length = 0;
	unsigned char cid_value[32];
	int count,i,j;

	memset( buf, 0, 32 );
	memset( cid_value, 0, 32 );

	//write 0x11 to pipe
	retval = bulk_write( dev, pipe, &data, 1);
	if(retval) {
		goto error;
	}

	//read CID from USBCID
	retval = bulk_read( dev, pipe, buf, sizeof(buf) );
	if(retval) {
		goto error;
	}

	//extract CID value
	count = 0;
	while(1) {
		if( buf[count] == 0x02 ) {
			count++;
			cid_length = (unsigned int)buf[count];
			count++;
			break;
		}
		if( count == 32 ) break;
		count++;
	}
	
	if(cid_length <= 30) {
		j = 0;
		for( i = count; i < (cid_length + count); i++ ) {
			cid_value[j++] = buf[i];
		}	//set cid_value
	}
	
	memcpy( data_buffer, cid_value, sizeof(cid_value) );	//copy CID value to ctrl_info
	return 0;
error:
	return retval;
}

static int cid_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
	int retval = 0;
	cid_ctrl_info ctrl_info;	
	struct usb_cid *dev = (struct usb_cid *)filp->private_data;
	
	memset( &ctrl_info, 0, sizeof(cid_ctrl_info) );
	if( copy_from_user( (void*)&ctrl_info, (const void*)arg, sizeof(cid_ctrl_info) ) ) return -EFAULT;	

	if( _IOC_TYPE(cmd) != IOCTL_EZUSB_MAGIC ) return -EINVAL;
	if( _IOC_NR(cmd) >= IOCTL_EZUSB_MAXNR ) return -EINVAL;

	switch(cmd) {
		case IOCTL_EZUSB_READ_CID:
			if( check_bulk_ep(ctrl_info.pipe) == 0 ) return -EINVAL;
			retval = read_cid(dev, ctrl_info.pipe-1, ctrl_info.buff);
			if(retval) goto error;
			if( copy_to_user( (void *)arg, (const void *)&ctrl_info, sizeof(cid_ctrl_info) ) ) {
				retval = -EFAULT;
				goto error;
			}	//copy ctrl_info to user space
			break;
		case IOCTL_EZUSB_BULK_READ :
			if( check_bulk_ep(ctrl_info.pipe) == 0 ) return -EINVAL;
			retval = bulk_read(dev, ctrl_info.pipe-1, ctrl_info.buff, ctrl_info.length);
			if(retval) goto error;			
			if( copy_to_user( (void *)arg, (const void *)&ctrl_info, sizeof(cid_ctrl_info) ) ) {
				retval = -EFAULT;
				goto error;
			}	//copy ctrl_info to user space
			break;	
		case IOCTL_EZUSB_BULK_WRITE :
			if( check_bulk_ep(ctrl_info.pipe) == 0 ) return -EINVAL;
			retval = bulk_write(dev, ctrl_info.pipe, ctrl_info.buff, ctrl_info.length);
			if(retval) goto error;
			break;
	}
	
	return 0;
error:
	return retval;
}

static struct file_operations cid_fops = {
	.owner =	THIS_MODULE,
	.ioctl =	cid_ioctl,
	.open =		cid_open,
	.release =	cid_release,
};

static struct usb_class_driver cid_class = {
	.name = 	"cid%d",
	.fops =		&cid_fops,
	.minor_base =	USBCID_MINOR,
};

static int cid_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
	int i = 0;
	int retval = 0;
	int bulk_in_counter = 0, bulk_out_counter = 0;
	
	struct usb_cid *dev = NULL;
	struct usb_host_interface *iface_desc;
	struct usb_endpoint_descriptor *endpoint;
	
	dev = kmalloc( sizeof(struct usb_cid), GFP_KERNEL );
	if(dev == NULL) {
		err("USBCID: Out of memory");
		goto error;
	}
	
	dev->udev = usb_get_dev( interface_to_usbdev(interface) );
	dev->interface = interface;

	//Find endpoins
	iface_desc = interface->cur_altsetting;
	for( i = 0; i < iface_desc->desc.bNumEndpoints; ++i ) {
		endpoint = &iface_desc->endpoint[i].desc;

		//case: Bulk
		if( (endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK ) {
			if( endpoint->bEndpointAddress & USB_DIR_IN ) {
				if( bulk_in_counter != 4 ) dev->bulk_in_endpointAddr[bulk_in_counter++] = endpoint->bEndpointAddress;
			}
			else {
				if( bulk_out_counter != 4 ) dev->bulk_out_endpointAddr[bulk_out_counter++] = endpoint->bEndpointAddress;
			}
		}
		
		if( (bulk_in_counter == 4 ) && (bulk_out_counter == 4 ) ) break;
	}
	if( (bulk_in_counter != 4) || (bulk_out_counter != 4) ) 
			printk("USBCID: Insufficient endpoints - %d %d %d\n", 
				iface_desc->desc.bNumEndpoints, bulk_in_counter, bulk_out_counter);
	usb_set_intfdata(interface, dev);
	
	retval = usb_register_dev(interface, &cid_class);
	if(retval) {
		err("USBCID: Unable to get a minor for this device");
		usb_set_intfdata(interface, NULL);
		goto error;
	}
	
	printk("USBCID: Success - cid_probe()\n");
	return 0;
error:
	kfree(dev);
	return retval;
}

static void cid_disconnect(struct usb_interface* interface)
{
	struct usb_cid *dev;
	
	lock_kernel();
	
	dev = usb_get_intfdata(interface);	
	usb_set_intfdata(interface, NULL);
	usb_deregister_dev(interface, &cid_class);
	printk("USBCID: Success - cid_disconnect()\n");
	
	unlock_kernel();
	
	kfree(dev);
}

static struct usb_driver cid_driver = {
	.name =		"usbcid",
	.probe =	cid_probe,
	.disconnect =	cid_disconnect,
	.id_table =	id_table,
};

static int __init usb_cid_init(void) 
{
	int retval=0;

	retval=usb_register(&cid_driver);
	if(retval) {
		err("USBCID: usb_register failed. Error number %d", retval);
	}

	return retval;
}

static void __exit usb_cid_exit(void) 
{
	usb_deregister(&cid_driver);
}

module_init(usb_cid_init);
module_exit(usb_cid_exit);

MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");

